﻿using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MiniFox;
using MiniFox.Caching;
using MiniFox.Components;
using MiniFox.Infrastructure;
using MiniFox.Platform;
using MiniFox.Platform.Exceptions;
using MiniFox.Platform.OAuth2;
using MiniFox.Platform.OAuth2.Models;
using MiniFox.Platform.OAuth2.Web;
using MiniFox.Platform.OAuth2.Web.Filters;
using MiniFox.Platform.Resources;
using MiniFox.Resources;
using MiniFox.Web;
using MiniFox.Web.Infrastructure;
using System.ComponentModel.DataAnnotations;
using DingTalk.Core;
using DingTalk.Core.Models;
using DingTalk.Corp.Auth.Models;
using DingTalk.Corp.Auth.Services;

namespace DingTalk.Corp.Auth
{
    /// <summary>
    /// dingTalk open auth 2.0 agent api, （~/ding/auth）
    /// </summary>
    [AllowAnonymous, NoCache, PassportFilter, Area(ApiGlobalKey.AREA_NAME)]
    public class CorpAuthController : OAuth2ControllerBase<IPassportService>
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        protected virtual IDingTalkAgentAuthService CreateAgentService(string name)
        {
            return ComponentFactory.GetComponent<IDingTalkAgentAuthService>(name);
        }


        private RedirectResponse Connect(string name, string ticket, AuthMode? authMode, bool scanned)
        {
            var state = SequenceGUID.NewGUID();
            Ticket authTicket = ticket;
            CacheService.DefaultCachingProvider.Add(state, ticket, authTicket.Duration);
            RedirectResponse response = new RedirectResponse();
            string callbackUrl = WebUtil.BuildFullUrl(this.Request.GetFullApplicationPath(), this.Url.Action(scanned ? "DeviceAuth" : "signIn", new { name }));

            var corpService = this.CreateAgentService(name);
            response.RedirectUri = corpService.CreateRedirectUri(callbackUrl, state, authMode ?? AuthMode.Default);

            return response;
        }


        #region 网页授权

        #region 钉钉过渡跳转

        /// <summary>
        /// 移动端H5跳转授权
        /// </summary>
        /// <param name="name"></param>
        /// <param name="authTicket"></param>
        /// <param name="authMode"></param>
        /// <returns></returns>
        [HttpGet]
        public IActionResult Connect(
            [FromRoute(Name = GlobalKey.NAME)] string name, [FromQuery(Name = OpenAuthKey.AUTH_TICKET), Required(ErrorMessageResourceType = typeof(CommonMessages), ErrorMessageResourceName = "MSG_CommonRequired")] string authTicket,
            [FromQuery] AuthMode? authMode)
        {
            var response = this.Connect(name, authTicket, authMode, false);
            return this.ResponseResult(response);
        }
        #endregion

        #region 钉钉授权码换取钉钉身份信息
        private async Task<ExternalOpenAuthUser> GetAuthUserAsync(string name, string code, DingTalkClientType clientType)
        {
            ExternalOpenAuthUser authUser;
            string unionId = null;
            object userData = null;
            var agentService = this.CreateAgentService(name);
            switch (clientType)
            {
                case DingTalkClientType.MiniApp:
                    var session = await agentService.GetSessionAsync(code);
                    unionId = session.UserID;
                    userData = session;
                    break;
                case DingTalkClientType.H5:
                default:
                    var user = await agentService.GetUserInfoAsync(code);
                    unionId = user.UnionID;
                    userData = user;
                    break;
            }
            if (string.IsNullOrWhiteSpace(unionId))
            {
                throw new MiniFox.Exceptions.SystemException("登录失败, 钉钉获取用户信息有误, 请尝试使用其它方式登录");
            }

            authUser = new ExternalOpenAuthUser(unionId, ApiGlobalKey.USER_AGENT_NAME)
            {
                UserData = userData
            };

            return authUser;

        }
        #endregion

        #region 回传钉钉授权码

        private async Task<bool> SignInAsync(string name, string authTicket, string code, DingTalkClientType clientType)
        {
            ExternalOpenAuthUser authUser = CacheService.DefaultCachingProvider.Get<ExternalOpenAuthUser>(code);
            if (authUser == null)
            {
                authUser = await this.GetAuthUserAsync(name, code, clientType);
                CacheService.DefaultCachingProvider.Add(code, authUser, 60);
            }
            return this.Component.SignIn(authTicket, authUser);
        }
        /// <summary>
        /// 在PC端(钉钉扫码授权)或者移动端钉钉直接跳转授权认证后，通过接收钉钉授权码登录
        /// </summary>
        /// <param name="name"></param>
        /// <param name="code">钉钉授权码</param>
        /// <param name="state"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<IActionResult> SignInAsync([FromRoute(Name = GlobalKey.NAME)] string name, [FromQuery] string code, [FromQuery] string state)
        {
            //从缓存获取移动端票据
            Ticket authTicket = CacheService.DefaultCachingProvider.Get<string>(state);
            CacheService.DefaultCachingProvider.Remove(state);

            if (authTicket == null || authTicket.IsExpired())
            {
                throw new PlatformException(ResMessage.ERR_InvalidAuthTicket);
            }

            RedirectResponse response = new RedirectResponse();
            try
            {
                if (await this.SignInAsync(name, authTicket, code, DingTalkClientType.H5))
                {
                    string redirectUri = this.Component.CreateAuthUri(authTicket);
                    response.RedirectUri = string.IsNullOrEmpty(redirectUri) ? "/" : redirectUri;
                }
                else
                {
                    throw new PlatformException("ERR_Unauthorized");
                }
            }
            catch (UnregisteredException e)
            {
                response.RedirectUri = WebUtil.BuildRelativeUrl(this.Url.Content("~/passport/bind"), new { auth_ticket = authTicket });
            }

            return this.Redirect(response);
        }
        #endregion

        #region 小程序
        /// <summary>
        /// 钉钉小程序收授权码
        /// </summary>
        /// <param name="name"></param>
        /// <param name="authTicket"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<Response> SignInAsync([FromRoute(Name = GlobalKey.NAME)] string name, [FromQuery(Name = OpenAuthKey.AUTH_TICKET), Required(ErrorMessageResourceType = typeof(CommonMessages), ErrorMessageResourceName = "MSG_CommonRequired")] string authTicket, [FromBody] DingTalkSignIn model)
        {
            Response response = new Response();
            if (!await this.SignInAsync(name, authTicket, model.AuthCode, model.ClientType))
            {
                response.Message = ResMessage.ERR_UnmatchedUser;
            }
            return response;
        }

        #endregion

        #endregion

        #region 一码通扫码

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="authTicket"></param>
        /// <returns></returns>
        [HttpGet]
        public IActionResult DeviceConnect([FromRoute(Name = GlobalKey.NAME)] string name, [FromQuery(Name = OpenAuthKey.AUTH_TICKET), Required(ErrorMessageResourceType = typeof(CommonMessages), ErrorMessageResourceName = "MSG_CommonRequired")] string authTicket)
        {
            RedirectResponse response = this.Connect(name, authTicket, AuthMode.Default, true);
            return this.ResponseResult(response);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="code"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<IActionResult> DeviceAuthAsync([FromRoute(Name = GlobalKey.NAME)] string name, [FromQuery] string code, [FromQuery] string state)
        {
            Ticket authTicket = CacheService.DefaultCachingProvider.Get<string>(state);
            CacheService.DefaultCachingProvider.Remove(state);

            if (authTicket == null || authTicket.IsExpired())
            {
                throw new PlatformException(ResMessage.ERR_InvalidAuthTicket);
            }

            var user = await this.GetAuthUserAsync(name, code, DingTalkClientType.H5);
            this.Component.SignIn(authTicket, user, true);
            string redirectUri = this.Url.Action(nameof(DeviceResult), new { name, auth_ticket = authTicket.ToString() });

            return View(new { redirectUri });
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="ticket"></param>
        /// <returns></returns>
        [HttpGet]
        public IActionResult DeviceResult([FromRoute(Name = GlobalKey.NAME)] string name, [FromQuery(Name = OpenAuthKey.AUTH_TICKET)] string ticket)
        {
            if (string.IsNullOrEmpty(ticket))
            {
                throw new PlatformException(ResMessage.ERR_InvalidTicket);
            }
            Ticket authTicket = ticket;
            if (authTicket.IsExpired())
            {
                throw new PlatformException(ResMessage.ERR_InvalidAuthTicket);
            }
            //
            this.Component.DeviceAuthorized(authTicket);

            return View();
        }
        #endregion

    }
}
